package org.cafeanimal.sqlite;

import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Optional;

import org.cafeanimal.sqlite.util.Strings;


/**
 * Represents a database table schema.
 * 
 * @author michael
 */
public class TableDef {

    private final Params params;

    /**
     * Package private constructor called by {@linkplain TableDefBuilder}.
     * 
     * @param params parameters assembled by the builder
     */
    TableDef(Params params) {
        this.params = params;
    }
    
    /**
     * Package private copy constructor used by {@linkplain Database}.
     * 
     * @param tableDef table definition to copy
     */
    TableDef(TableDef tableDef) {
        this.params = new Params(tableDef.params);
    }

    public String getName() {
        return params.name;
    }

    public int getNumFields() {
        return params.fields.size();
    }
    
    public Field getField(int index) {
        return params.fields.get(index);
    }

    public Optional<Field> getField(String key) {
        for (Field f : params.fields) {
            if (f.getName().equalsIgnoreCase(key)) {
                return Optional.of(f);
            }
        }
        
        return Optional.absent();
    }

    public String getCreateTableSQL() {
        StringBuilder sb = new StringBuilder("CREATE TABLE ").append(params.name);
        
        List<String> fldSpecs = new ArrayList<String>();
        for (Field fld : params.fields) {
            fldSpecs.add(fld.getName() + " " + fld.getType().getTypeName());
        }
        
        sb.append(" (").append(Strings.concat(fldSpecs, ", "));
        
        String s = getPrimaryKeyString();
        if (!s.isEmpty()) {
            sb.append(", ").append(s);
        }
        
        s = getUniqueConstraintsString();
        if (!s.isEmpty()) {
            sb.append(", ").append(s);
        }

        sb.append(")");
        
        String sql = sb.toString();
        System.out.println(sql);
        return sql;
    }

    private String getPrimaryKeyString() {
        List<String> keyNames = new ArrayList<String>();
        for (Field fld : params.fields) {
            if (fld.isPrimaryKey()) {
                keyNames.add(fld.getName());
            }
        }
        
        if (!keyNames.isEmpty()) {
            StringBuilder sb = new StringBuilder("PRIMARY KEY(");
            sb.append(Strings.concat(keyNames, ", ")).append(")");
            return sb.toString();
        }
        
        return "";
    }
    
    private String getUniqueConstraintsString() {
        if (params.constraints.isEmpty()) {
            return "";
        }
        
        List<String> slist = new ArrayList<String>();
        for (Constraint c : params.constraints) {
            if (c instanceof UniqueConstraint) {
                slist.add(c.getSQL());
            }
        }
        
        if (slist.isEmpty()) {
            return "";
        } else {
            return Strings.concat(slist, ", ");
        }
    }

    /*
     * Package private class used by TableDef and TableDefBuilder.
     * Field values should only be set by TableDefBuilder or the copy 
     * constructor. Once an instance is enclosed in a TableDef it is
     * treated as immutable.
     */
    static class Params {
        String name;
        List<Field> fields;
        List<Constraint> constraints;
        boolean hasPrimaryKey;
        
        Params() {
            this.fields = new ArrayList<Field>();
            this.constraints = new ArrayList<Constraint>();
        }

        /**
         * Copy constructor.
         * 
         * @param other instance to copy
         */
        Params(Params other) {
            this.name = other.name;
            this.fields = new ArrayList<Field>();
            this.constraints = new ArrayList<Constraint>();
        
            for (Field fld : other.fields) {
                // Field is immutable so we can share an instance
                this.fields.add(fld);
            }
            
            for (Constraint cnst : other.constraints) {
                // Constraint is immutable
                this.constraints.add(cnst);
            }
            
            this.hasPrimaryKey = other.hasPrimaryKey;
        }
    }
}
